#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"


#include <iostream>
#include <math.h>

void processInput(GLFWwindow *window)
{
    if(glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

static int complieShader(unsigned int type, const std::string& source)
{
    unsigned int id = glCreateShader(type);

    const char* src = source.c_str();
    /**pass the GLSL source code into the shader*/
    glShaderSource(id, 1, &src, nullptr);
    /**compile the source code*/
    glCompileShader(id);

    // TODO: Error handling 
    int result;
    glGetShaderiv(id, GL_COMPILE_STATUS, &result);
    if(result == false)
    {
        int length;
        glGetShaderiv(id, GL_INFO_LOG_LENGTH, &length);
        char* message = (char*)alloca(length * sizeof(char));
        glGetShaderInfoLog(id , length, &length, message);
        std::cout << "fail to complie shader : "<<
        ((type == GL_VERTEX_SHADER)?"GL_VERTEX_SHADER":"GL_FRAGMENT_SHADER")<< 
        message << std::endl;

        glDeleteShader(id);
        return 0;
    }
    return id;
}


static unsigned int createShader(const std::string& vertex_shader, 
                        const std::string& fragment_shader)
{
    /**complie the GLSL code*/
    unsigned int vs = complieShader(GL_VERTEX_SHADER, vertex_shader);
    unsigned int fs = complieShader(GL_FRAGMENT_SHADER, fragment_shader);
    
    unsigned int program = glCreateProgram(); 
    glAttachShader(program, vs);
    glAttachShader(program, fs);
    glLinkProgram(program);

    glValidateProgram(program);

    glDeleteShader(vs);
    glDeleteShader(fs);

    return program;
}

int main()
{

    /**1.create windows*/
    // glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    // glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    //glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    /**size and name of the window*/
    GLFWwindow* window ;
    /**initialization*/
    glfwInit();
    
    window = glfwCreateWindow(800, 600, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }
    /**location of the lower left corner, set the width and height of the rendering window*/
    glViewport(0, 0, 800, 600);
    glEnable(GL_DEPTH_TEST);  

    /**2.generate buffer and bind it to the vertexes*/
    float positions[] = {//postion color texture
    /**fore-head*/
        -0.5f, -0.5f, 0.5f,  123.0f/255, 104.0f/255, 238.0f/255, 1.0f,//1.0f, 1.0f,//!bottom-left  0
        -0.5f,  0.5f, 0.5f,  1.0f,       1.0f,       0.0f,       1.0f,//1.0f, 0.0f,//!upper -left  1
         0.5f,  0.5f, 0.5f,  1.0f,       1.0f,       1.0f,       1.0f,//0.0f, 0.0f,//!upper -right 2
         0.5f, -0.5f, 0.5f,  1.0f,       0.0f,       0.0f,       1.0f,//0.0f, 1.0f,//!bottom-right 3
    /**back-head*/
        -0.5f, -0.5f,-0.5f,  0.0f,       1.0f,       1.0f,       1.0f,//1.0f, 1.0f,//!bottom-left  4
        -0.5f,  0.5f,-0.5f,  0.0f,       1.0f,       0.0f,       1.0f,//1.0f, 0.0f,//!upper -left  5
         0.5f,  0.5f,-0.5f,  127.0f/255, 255.0f/255, 212.0f/255, 1.0f,//0.0f, 0.0f,//!upper -right 6
         0.5f, -0.5f,-0.5f,  0.0f,       0.0f,       1.0f,       1.0f,//0.0f, 1.0f //!bottom-right 7
    };

    unsigned int index_list[][4] = {
        0, 3, 2, 1,
        0, 3, 7, 4,
        0, 1, 5, 4,
        6, 2, 3, 7,
        6, 5, 1, 2,
        6, 7, 4, 5
    };


    /**allocate memory and bind the vertexs' postion to it*/
    unsigned int vertex_buffer;//!store all points
    unsigned int vertex_array; //!as a array
    unsigned int element_buffer; //! pointers for fetching the points
    
    glGenVertexArrays(1, &vertex_array);
    glGenBuffers(1,&vertex_buffer);
    glGenBuffers(1,&element_buffer);

    glBindVertexArray(vertex_array);

    glBindBuffer(GL_ARRAY_BUFFER,vertex_buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(positions), positions, GL_STATIC_DRAW);

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(index_list), index_list, GL_STATIC_DRAW);

    /**@
     * @param the size of the vertex attribute.
     * @
    */
   /**position attribute*/
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    /**color attribute*/
    glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, 7 * sizeof(float), (void*)(3* sizeof(float)));
    glEnableVertexAttribArray(1);
    /**texture attribute*/
    // glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
    // glEnableVertexAttribArray(2);


    //std::cout << "buffer : " << buffer << std::endl;

    // /**add texture*/
    // unsigned int texture;
    // glGenTextures(1, &texture); 
    // glBindTexture(GL_TEXTURE_2D, texture);
    // /**set the texture wrapping and filtering options*/
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  
    // int width, height, nrChannels;
    // unsigned char *data = stbi_load("../images/container.jpg", &width, &height, &nrChannels, 0); 

    // if(data)
    // {
    //     glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    //     glGenerateMipmap(GL_TEXTURE_2D);  
    // }
    // else
    // {
    //     std::cout << "failed to load texture" << std::endl;
    //     return -1;
    // }
    

    
    /**create shader*/
    std::string vertex_shader = 
    "#version 330 core\n"
    "layout (location = 0) in vec3 position;\n"
    "layout (location = 1) in vec4 color;\n"
    //"layout (location = 2) in vec2 text_coord;\n"
    "out vec4 vertexColor;\n"
    //"out vec2 text_coord_out;\n"
    "uniform mat4 model;\n"
    "uniform mat4 view;\n"
    "uniform mat4 projection;\n"
    "uniform mat4 transform;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = projection * view * model * vec4(position, 1.0);\n"//
    "   vertexColor = color;"
    //"   text_coord_out = text_coord;"
    "}\n";

    std::string fragment_shader = 
    "#version 330 core\n"
    "out vec4 frag_color;\n"
    "in vec4 vertexColor\n;"//"uniform  vec4 vertexColor;\n"
    //"in vec2 text_coord_out;\n"
    //"uniform sampler2D ourTexture;\n"
    "void main()\n"
    "{\n"
    "   frag_color = vertexColor;\n"//texture(ourTexture, text_coord_out);\n"
    "}\n";

    unsigned int shader = createShader(vertex_shader, fragment_shader);

    glUseProgram(shader);



    while(!glfwWindowShouldClose(window))
    {
        /**checks if GLFW has been instructed to close*/
        processInput(window);

        /**render here*/
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        //glClear(GL_COLOR_BUFFER_BIT);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glm::mat4 view = glm::mat4(1.0f);
        glm::mat4 projection;

        // note that we're translating the scene in the reverse direction of where we want to move
        view = glm::translate(view, glm::vec3(0.0f, 0.0f, -3.0f)); 
        projection = glm::perspective(glm::radians(45.0f), 800.0f / 600.0f, 0.1f, 100.0f);

        /**color change with time*/
        //float timeValue = glfwGetTime();
        //float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
        // int vertexColorLocation = glGetUniformLocation(shader, "vertexColor");
        // glUseProgram(shader);
        // glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
        
        glm::mat4 model = glm::mat4(1.0f);
        model = glm::rotate(model, (float)glfwGetTime() * glm::radians(50.0f), glm::vec3(0.5f, 1.0f, 0.0f));


        float timeValue = glfwGetTime();
        float rotate_angle = sin(timeValue) * 90.0f;

        // glm::mat4 trans = glm::mat4(1.0f);
        // trans = glm::scale(trans, glm::vec3(0.5, 0.5, 0.5)); 
        // //rotate by vertical line / horizontal line / z-axises
        // trans = glm::rotate(trans, glm::radians(rotate_angle), glm::vec3(0.0, 1.0, 1.0));

        // /**transform*/
        // unsigned int transformLoc = glGetUniformLocation(shader, "transform");
        // glUniformMatrix4fv(transformLoc, 1, GL_FALSE, glm::value_ptr(trans));

        int modelLoc = glGetUniformLocation(shader, "model");
        glUniformMatrix4fv(modelLoc, 1, GL_FALSE, glm::value_ptr(model));
        int viewLoc = glGetUniformLocation(shader, "view");
        glUniformMatrix4fv(viewLoc, 1, GL_FALSE, glm::value_ptr(view));
        int projectionLoc = glGetUniformLocation(shader, "projection");
        glUniformMatrix4fv(projectionLoc, 1, GL_FALSE, glm::value_ptr(projection));

        //glBindTexture(GL_TEXTURE_2D, texture);
        glBindVertexArray(vertex_array);

        glEnableClientState(GL_VERTEX_ARRAY);
        glVertexPointer(3, GL_FLOAT, 0, positions);

        //glDrawArrays(GL_TRIANGLES, 0, 36);
        glDrawElements(GL_QUADS, 24, GL_UNSIGNED_INT, 0);
        glfwSwapBuffers(window);
        /**checks if any events are triggered*/
        glfwPollEvents();    
    }
  
    glfwTerminate();
    return 0;
}